/**********************************************************\
 |                                                          |
 |                          hprose                          |
 |                                                          |
 | Official WebSite: http://www.hprose.com/                 |
 |                   http://www.hprose.org/                 |
 |                                                          |
 \**********************************************************/
/**********************************************************\
 *                                                        *
 * ValueWriter.java                                       *
 *                                                        *
 * value writer class for Java.                           *
 *                                                        *
 * LastModified: Aug 16, 2016                             *
 * Author: Ma Bingyao <andot@hprose.com>                  *
 *                                                        *
 \**********************************************************/
package net.hasor.libs.com.hprose.io.serialize;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Calendar;

import static net.hasor.libs.com.hprose.io.HproseTags.*;
public final class ValueWriter {
    private final static byte[]              digits     = {//
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'//
    };
    private final static byte[]              DigitTens  = {//
            '0', '0', '0', '0', '0', '0', '0', '0', '0', '0',//
            '1', '1', '1', '1', '1', '1', '1', '1', '1', '1',//
            '2', '2', '2', '2', '2', '2', '2', '2', '2', '2',//
            '3', '3', '3', '3', '3', '3', '3', '3', '3', '3',//
            '4', '4', '4', '4', '4', '4', '4', '4', '4', '4',//
            '5', '5', '5', '5', '5', '5', '5', '5', '5', '5',//
            '6', '6', '6', '6', '6', '6', '6', '6', '6', '6',//
            '7', '7', '7', '7', '7', '7', '7', '7', '7', '7',//
            '8', '8', '8', '8', '8', '8', '8', '8', '8', '8',//
            '9', '9', '9', '9', '9', '9', '9', '9', '9', '9'//
    };
    private final static byte[]              DigitOnes  = {//
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',//
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',//
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',//
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',//
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',//
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',//
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',//
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',//
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',//
            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9'//
    };
    private final static byte[]              minIntBuf  = new byte[] {//
            '-', '2', '1', '4', '7', '4', '8', '3', '6', '4', '8'//
    };
    private final static byte[]              minLongBuf = new byte[] {//
            '-', '9', '2', '2', '3', '3', '7', '2', '0', '3',//
            '6', '8', '5', '4', '7', '7', '5', '8', '0', '8'//
    };
    private final static ThreadLocal<byte[]> buffer     = new ThreadLocal<byte[]>() {
        @Override
        protected byte[] initialValue() {
            return new byte[20];
        }
    };
    public final static void writeInt(OutputStream stream, int i) throws IOException {
        if ((i >= 0) && (i <= 9)) {
            stream.write((byte) ('0' + i));
        } else if (i == Integer.MIN_VALUE) {
            stream.write(minIntBuf);
        } else {
            byte[] buf = buffer.get();
            int off = 20;
            int q, r;
            byte sign = 0;
            if (i < 0) {
                sign = '-';
                i = -i;
            }
            while (i >= 65536) {
                q = i / 100;
                r = i - (q * 100);
                i = q;
                buf[--off] = DigitOnes[r];
                buf[--off] = DigitTens[r];
            }
            for (; ; ) {
                q = (i * 52429) >>> (16 + 3);
                r = i - (q * 10);
                buf[--off] = digits[r];
                i = q;
                if (i == 0)
                    break;
            }
            if (sign != 0) {
                buf[--off] = sign;
            }
            stream.write(buf, off, 20 - off);
        }
    }
    public final static void writeInt(OutputStream stream, long i) throws IOException {
        if ((i >= 0) && (i <= 9)) {
            stream.write((byte) ('0' + i));
        } else if (i == Long.MIN_VALUE) {
            stream.write(minLongBuf);
        } else {
            byte[] buf = buffer.get();
            long q;
            int off = 20;
            int q2, r;
            byte sign = 0;
            if (i < 0) {
                sign = '-';
                i = -i;
            }
            while (i > Integer.MAX_VALUE) {
                q = i / 100;
                r = (int) (i - (q * 100));
                i = q;
                buf[--off] = DigitOnes[r];
                buf[--off] = DigitTens[r];
            }
            int i2 = (int) i;
            while (i2 >= 65536) {
                q2 = i2 / 100;
                r = i2 - (q2 * 100);
                i2 = q2;
                buf[--off] = DigitOnes[r];
                buf[--off] = DigitTens[r];
            }
            for (; ; ) {
                q2 = (i2 * 52429) >>> (16 + 3);
                r = i2 - (q2 * 10);
                buf[--off] = digits[r];
                i2 = q2;
                if (i2 == 0)
                    break;
            }
            if (sign != 0) {
                buf[--off] = sign;
            }
            stream.write(buf, off, 20 - off);
        }
    }
    public final static void write(OutputStream stream, int i) throws IOException {
        if (i >= 0 && i <= 9) {
            stream.write(i + '0');
        } else {
            stream.write(TagInteger);
            writeInt(stream, i);
            stream.write(TagSemicolon);
        }
    }
    public final static void write(OutputStream stream, long l) throws IOException {
        if (l >= 0 && l <= 9) {
            stream.write((int) l + '0');
        } else {
            stream.write(TagLong);
            writeInt(stream, l);
            stream.write(TagSemicolon);
        }
    }
    public final static void write(OutputStream stream, boolean b) throws IOException {
        stream.write(b ? TagTrue : TagFalse);
    }
    public final static void write(OutputStream stream, float f) throws IOException {
        if (Float.isNaN(f)) {
            stream.write(TagNaN);
        } else if (Float.isInfinite(f)) {
            stream.write(TagInfinity);
            stream.write(f > 0 ? TagPos : TagNeg);
        } else {
            stream.write(TagDouble);
            stream.write(getAscii(Float.toString(f)));
            stream.write(TagSemicolon);
        }
    }
    public final static void write(OutputStream stream, double d) throws IOException {
        if (Double.isNaN(d)) {
            stream.write(TagNaN);
        } else if (Double.isInfinite(d)) {
            stream.write(TagInfinity);
            stream.write(d > 0 ? TagPos : TagNeg);
        } else {
            stream.write(TagDouble);
            stream.write(getAscii(Double.toString(d)));
            stream.write(TagSemicolon);
        }
    }
    public final static void write(OutputStream stream, BigInteger bi) throws IOException {
        stream.write(TagLong);
        stream.write(getAscii(bi.toString()));
        stream.write(TagSemicolon);
    }
    public final static void write(OutputStream stream, BigDecimal bd) throws IOException {
        stream.write(TagDouble);
        stream.write(getAscii(bd.toString()));
        stream.write(TagSemicolon);
    }
    public final static void write(OutputStream stream, char c) throws IOException {
        stream.write(TagUTF8Char);
        if (c < 0x80) {
            stream.write(c);
        } else if (c < 0x800) {
            stream.write(0xc0 | (c >>> 6));
            stream.write(0x80 | (c & 0x3f));
        } else {
            stream.write(0xe0 | (c >>> 12));
            stream.write(0x80 | ((c >>> 6) & 0x3f));
            stream.write(0x80 | (c & 0x3f));
        }
    }
    public final static void write(OutputStream stream, char[] s) throws IOException {
        int length = s.length;
        if (length > 0) {
            writeInt(stream, length);
        }
        stream.write(TagQuote);
        stream.write(new String(s).getBytes("UTF-8"));
/*
        byte[] b = new byte[length * 3];
        int n = 0;
        for (int i = 0; i < length; ++i) {
            int c = 0xffff & s[i];
            if (c < 0x80) {
                b[n++] = (byte)c;
            }
            else if (c < 0x800) {
                b[n++] = (byte)(0xc0 | (c >>> 6));
                b[n++] = (byte)(0x80 | (c & 0x3f));
            }
            else if (c < 0xd800 || c > 0xdfff) {
                b[n++] = (byte)(0xe0 | (c >>> 12));
                b[n++] = (byte)(0x80 | ((c >>> 6) & 0x3f));
                b[n++] = (byte)(0x80 | (c & 0x3f));
            }
            else {
                if (++i < length) {
                    int c2 = 0xffff & s[i];
                    if (c < 0xdc00 && 0xdc00 <= c2 && c2 <= 0xdfff) {
                        c = ((c & 0x03ff) << 10 | (c2 & 0x03ff)) + 0x010000;
                        b[n++] = (byte)(0xf0 | (c >>> 18));
                        b[n++] = (byte)(0x80 | ((c >>> 12) & 0x3f));
                        b[n++] = (byte)(0x80 | ((c >>> 6) & 0x3f));
                        b[n++] = (byte)(0x80 | (c & 0x3f));
                    }
                    else {
                        throw new HproseException("wrong unicode string");
                    }
                }
                else {
                    throw new HproseException("wrong unicode string");
                }
            }
        }
        stream.write(b, 0, n);
*/
        stream.write(TagQuote);
    }
    public final static void write(OutputStream stream, String s) throws IOException {
        int length = s.length();
        if (length > 0) {
            writeInt(stream, length);
        }
        stream.write(TagQuote);
        stream.write(s.getBytes("UTF-8"));
/*
        byte[] b = new byte[length * 3];
        int n = 0;
        for (int i = 0; i < length; ++i) {
            int c = 0xffff & s.charAt(i);
            if (c < 0x80) {
                b[n++] = (byte)c;
            }
            else if (c < 0x800) {
                b[n++] = (byte)(0xc0 | (c >>> 6));
                b[n++] = (byte)(0x80 | (c & 0x3f));
            }
            else if (c < 0xd800 || c > 0xdfff) {
                b[n++] = (byte)(0xe0 | (c >>> 12));
                b[n++] = (byte)(0x80 | ((c >>> 6) & 0x3f));
                b[n++] = (byte)(0x80 | (c & 0x3f));
            }
            else {
                if (++i < length) {
                    int c2 = 0xffff & s.charAt(i);
                    if (c < 0xdc00 && 0xdc00 <= c2 && c2 <= 0xdfff) {
                        c = ((c & 0x03ff) << 10 | (c2 & 0x03ff)) + 0x010000;
                        b[n++] = (byte)(0xf0 | (c >>> 18));
                        b[n++] = (byte)(0x80 | ((c >>> 12) & 0x3f));
                        b[n++] = (byte)(0x80 | ((c >>> 6) & 0x3f));
                        b[n++] = (byte)(0x80 | (c & 0x3f));
                    }
                    else {
                        throw new HproseException("wrong unicode string");
                    }
                }
                else {
                    throw new HproseException("wrong unicode string");
                }
            }
        }
        stream.write(b, 0, n);
*/
        stream.write(TagQuote);
    }
    public final static void writeDate(OutputStream stream, int year, int month, int day) throws IOException {
        stream.write(TagDate);
        stream.write((byte) ('0' + (year / 1000 % 10)));
        stream.write((byte) ('0' + (year / 100 % 10)));
        stream.write((byte) ('0' + (year / 10 % 10)));
        stream.write((byte) ('0' + (year % 10)));
        stream.write((byte) ('0' + (month / 10 % 10)));
        stream.write((byte) ('0' + (month % 10)));
        stream.write((byte) ('0' + (day / 10 % 10)));
        stream.write((byte) ('0' + (day % 10)));
    }
    public final static void writeDateOfCalendar(OutputStream stream, Calendar calendar) throws IOException {
        writeDate(stream, calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH) + 1, calendar.get(Calendar.DAY_OF_MONTH));
    }
    public final static void writeTime(OutputStream stream, int hour, int minute, int second, int millisecond, boolean ignoreZero, boolean ignoreMillisecond) throws IOException {
        if (ignoreZero && hour == 0 && minute == 0 && second == 0 && millisecond == 0) {
            return;
        }
        stream.write(TagTime);
        stream.write((byte) ('0' + (hour / 10 % 10)));
        stream.write((byte) ('0' + (hour % 10)));
        stream.write((byte) ('0' + (minute / 10 % 10)));
        stream.write((byte) ('0' + (minute % 10)));
        stream.write((byte) ('0' + (second / 10 % 10)));
        stream.write((byte) ('0' + (second % 10)));
        if (!ignoreMillisecond && millisecond > 0) {
            stream.write(TagPoint);
            stream.write((byte) ('0' + (millisecond / 100 % 10)));
            stream.write((byte) ('0' + (millisecond / 10 % 10)));
            stream.write((byte) ('0' + (millisecond % 10)));
        }
    }
    public final static void writeTimeOfCalendar(OutputStream stream, Calendar calendar, boolean ignoreZero, boolean ignoreMillisecond) throws IOException {
        writeTime(stream, calendar.get(Calendar.HOUR_OF_DAY), calendar.get(Calendar.MINUTE), calendar.get(Calendar.SECOND), calendar.get(Calendar.MILLISECOND), ignoreZero, ignoreMillisecond);
    }
    public final static void writeNano(OutputStream stream, int nanosecond) throws IOException {
        if (nanosecond > 0) {
            stream.write(TagPoint);
            stream.write((byte) ('0' + (nanosecond / 100000000 % 10)));
            stream.write((byte) ('0' + (nanosecond / 10000000 % 10)));
            stream.write((byte) ('0' + (nanosecond / 1000000 % 10)));
            if (nanosecond % 1000000 > 0) {
                stream.write((byte) ('0' + (nanosecond / 100000 % 10)));
                stream.write((byte) ('0' + (nanosecond / 10000 % 10)));
                stream.write((byte) ('0' + (nanosecond / 1000 % 10)));
                if (nanosecond % 1000 > 0) {
                    stream.write((byte) ('0' + (nanosecond / 100 % 10)));
                    stream.write((byte) ('0' + (nanosecond / 10 % 10)));
                    stream.write((byte) ('0' + (nanosecond % 10)));
                }
            }
        }
    }
    public final static byte[] getAscii(String s) {
        int size = s.length();
        byte[] b = new byte[size--];
        for (; size >= 0; --size) {
            b[size] = (byte) s.charAt(size);
        }
        return b;
    }
}
